﻿using System;
using System.Windows;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Diagnostics;
using ISRinterface;
using ISR.Config;

namespace ISR.Core {
	/// <summary>
	/// Application handler class.
	/// </summary>
	sealed class ISRApplication : IApplication {
		#region Private fields
		/// <summary>
		/// A collection of all application components.
		/// </summary>
		private Dictionary<string, IComponent> _components = null;

		/// <summary>
		/// A collection of all application modules.
		/// </summary>
		private Dictionary<string, IModule> _modules = null;

		/// <summary>
		/// An instance of user config.
		/// </summary>
		private UserConfig _userConfig = null;
		#endregion

		#region Constructors and singleton handling
		/// <summary>
		/// Application class instance.
		/// </summary>
		/// <remarks>
		/// It is instantiated in the static constructor.
		/// </remarks>
		private static volatile ISRApplication _instance = null;

		/// <summary>
		/// Initializes static members of the <see cref="ISRApplication" /> class.
		/// </summary>
		static ISRApplication() {
			_instance = new ISRApplication();
		}

		/// <summary>
		/// Prevents a default instance of the <see cref="ISRApplication" /> class from being created.
		/// </summary>
		private ISRApplication() {

		}

		/// <summary>
		/// Gets the application instance.
		/// </summary>
		/// /// <remarks>
		/// Application instance is instantiated in the static constructor.
		/// </remarks>
		/// <returns>ISRApplication</returns>
		public static ISRApplication getInstance() {
			if (_instance != null) {
				return _instance;
			}
			else {
				//TODO: trigger runtime exception here.
				throw new Exception();
			}
		}
		#endregion

		#region Console handling options
		/// <summary>
		/// Handle of the console window.
		/// </summary>
		private IntPtr _consoleHandle = IntPtr.Zero;

		/// <summary>
		/// Finds the window by its title.
		/// </summary>
		/// <param name="lpClassName">Name of the lp class.</param>
		/// <param name="lpWindowName">Name of the lp window.</param>
		/// <returns>IntPtr</returns>
		[DllImport("user32.dll")]
		public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

		/// <summary>
		/// Shows or hides the window.
		/// </summary>
		/// <param name="hWnd">The window pointer.</param>
		/// <param name="nCmdShow">Requested action.</param>
		/// <returns><c>true</c> if success, <c>false</c> otherwise</returns>
		[DllImport("user32.dll")]
		static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
		#endregion

		#region Public methods
		/// <summary>
		/// Gets the user config value.
		/// </summary>
		/// <typeparam name="T">Type of the user config value.</typeparam>
		/// <param name="name">The name of the user config value.</param>
		/// <returns>User config value of type T.</returns>
		/// <exception cref="UnknownConfigOption">Thrown if the config value hasn't been found.</exception>
		public T getConfigValue<T>(string name) {
			object o = _userConfig.getOption(name);

			if (o == null) {
				throw new UnknownConfigOption(); //Use of undefined config property.
			}
			else {
				return (T) o;
			}
		}

		/// <summary>
		/// Manages the application life cycle.
		/// </summary>
		/// <param name="args">Command line arguments.</param>
		public void run(string[] args) {
			//Application pre process.
			try {
				applicationPreprocess(args);
			}
			catch (Exception e) {
				showErrorInConsole(String.Format("Application pre-process failed with message:\n{0}", e.Message));
				return;
			}

			//Main process.
			try {
				mainProcess();
			}
			catch (Exception e) {
				showErrorInConsole(String.Format("Application triggered an unhandled error:\n{0}", e.Message));
				return;
			}

			//Application post process.
			try {
				applicationPostprocess();
			}
			catch (Exception e) {
				showErrorInConsole(String.Format("Application post-process failed with message:\n{0}", e.Message));
				return;
			}
		}

		/// <summary>
		/// Sets the user config value.
		/// </summary>
		/// <typeparam name="T">Type of the user config value.</typeparam>
		/// <param name="name">The name of the user config value.</param>
		/// <param name="value">The value of the user config value.</param>
		public void setConfigValue<T>(string name, T value) {
			_userConfig.setOption(name, (object) value);
		}
		#endregion

		#region Private methods
		/// <summary>
		/// Performs actions after application end.
		/// </summary>
		private void applicationPostprocess() {
			//Save user config.
			saveUserConfig();
		}

		/// <summary>
		/// Performs actions before application start.
		/// </summary>
		/// <param name="commandLineArguments">Command line arguments.</param>
		private void applicationPreprocess(string[] commandLineArguments) {
			//Set up the service console.
			Console.Title = AppConfig.serviceConsoleTitle;
			_consoleHandle = FindWindow(null, Console.Title);
#if RELEASE
			hideConsole();
#endif

			//Load user config.
			loadUserConfig();
		}

		/// <summary>
		/// Hides the service console.
		/// </summary>
		private void hideConsole() {
			if (_consoleHandle != IntPtr.Zero) {
				ShowWindow(_consoleHandle, 0);
			}
		}

		/// <summary>
		/// Initializes application components.
		/// </summary>
		private void initializeComponents() {
			_components = new Dictionary<string,IComponent>();
		}

		/// <summary>
		/// Initializes application modules.
		/// </summary>
		private void initializeModules() {
			_modules = new Dictionary<string, IModule>();
		}

		/// <summary>
		/// Loads the user config.
		/// </summary>
		/// <exception cref="ISR.Core.ISRException">Thrown if config file loading fails.</exception>
		private void loadUserConfig() {
			if (File.Exists(AppConfig.userConfigFile)) {
				object config = null;
				FileStream fs;

				try {
					fs = new FileStream(AppConfig.userConfigFile, FileMode.Open, FileAccess.Read);

					try {
						BinaryFormatter bf = new BinaryFormatter();
						config = bf.Deserialize(fs);
					}
					finally {
						fs.Close();
					}
				}
				catch (Exception e) {
					throw new ISRException("Failed to load user config file.", e);
				}

				//Deserializing succeeded, check if the content is UserConfig.
				if (config is UserConfig) {
					_userConfig = config as UserConfig;
				}
				else {
					throw new ISRException("Config file is corrupted.");
				}
			}
			else {
				//User config file doesn't exist, but it is required.
				throw new ISRException("Config file is missing.");
			}
		}

		/// <summary>
		/// Performs main application process.
		/// </summary>
		private void mainProcess() {
			//Initialization - components and modules loading.
			initializeComponents();
			initializeModules();

			//Start WPF application.
			WPFApplication WPFApp = new WPFApplication();
			WPFApp.InitializeComponent();
			WPFApp.Run();
		}

		/// <summary>
		/// Saves the user config.
		/// </summary>
		/// <exception cref="ISR.Core.ISRException">Thrown if config file saving fails.</exception>
		private void saveUserConfig() {
			//Check prerequisites.
			Debug.Assert(_userConfig is UserConfig);

			if (!(_userConfig is UserConfig)) {
				return; //User config is not loaded. It should not happen. Stop saving on production.
			}

			if (!_userConfig.hasChanges()) {
				try {
					File.SetLastWriteTime(AppConfig.userConfigFile, DateTime.Now); //There are no changes in the user config so we don't need to rewrite it.
				}
				catch (Exception) {
					//Ignore this exception.
				}

				return;
			}

			//User config is loaded and there are some changes to write.
			string tempConfigFileName = AppConfig.userConfigFile + "_temp";
			FileStream fs;

			try {
				fs = new FileStream(tempConfigFileName, FileMode.Create, FileAccess.Write); //Write temporary file first.

				try {
					BinaryFormatter bf = new BinaryFormatter();
					bf.Serialize(fs, _userConfig);
				}
				finally {
					fs.Close();
				}
			}
			catch (Exception e) {
				//Serializing user config failed.
				throw new ISRException("Failed to save user config file.", e);
			}

			try {
				//Serializing user config succeeded, rewrite original file.
				File.Delete(AppConfig.userConfigFile);
				File.Move(tempConfigFileName, AppConfig.userConfigFile);
				File.Delete(tempConfigFileName);
			}
			catch (Exception e) {
				throw new ISRException("Failed to rewrite user config file.", e);
			}

			_userConfig.unsetDirty();
		}

		/// <summary>
		/// Shows the service console.
		/// </summary>
		private void showConsole() {
			if (_consoleHandle != IntPtr.Zero) {
				ShowWindow(_consoleHandle, 1);
			}
		}

		/// <summary>
		/// Shows an error message in the service console.
		/// </summary>
		/// <param name="error">The error message.</param>
		private void showErrorInConsole(string error) {
			showConsole();
			Console.WriteLine(error);
			Console.WriteLine();
			Console.Write("Press any key...");
			Console.ReadLine();
		}
		#endregion
	}
}